#include "ParserAdd.h"

const string ParserAdd::regexInstructionAndArguments = "\\s*([a-zA-Z]+)\\s*(.*)";
const string ParserAdd::regexArgumentAddTimed = "\\s*([^>]+?)\\s*>"
											 "\\s*(\\S.*?)to\\s*(\\S.*?)\\s*";
const string ParserAdd::regexArgumentAddDeadline = "\\s*([^>]+?)\\s*>"
												"\\s*(\\S.*?)";
const string ParserAdd::regexArgumentAddFloating = "\\s*([^>]+?)\\s*";

const string ParserAdd::regexInstructionAdd = "(add)";

ParserAdd::ParserAdd(ParserBase* _nextParser) : ParserBase(_nextParser) {}

bool ParserAdd::tryParse(string userCommand, PARSED_COMMAND& parsedCommand){
	
	string instruction;
	string arguments;
	getInstructionAndArguments(userCommand, instruction, arguments);
	if (!checkInstruction(instruction)) {
		return false;
	}

	parseAdd(parsedCommand, arguments);

	return true;
}

bool ParserAdd::getInstructionAndArguments(const string& userCommand, 
										string& instruction, 
										string& arguments) {
	regex rgx(regexInstructionAndArguments);
	smatch matchResults;

	if (!regex_match(userCommand, matchResults, rgx)) {

		return false;
	}

	instruction = matchResults[INSTRUCTION_POS];
	if (matchResults.size() > ARGUMENTS_POS) {
		arguments = matchResults[ARGUMENTS_POS];
	}

	return true;
}

bool ParserAdd::checkInstruction(const string& instruction) {
	if (regex_match(instruction, regex(regexInstructionAdd,
									   tr1::regex_constants::icase))) {
		return true;
	}

	return false;
}

void ParserAdd::parseAdd(PARSED_COMMAND& parsedCommand,
					  const string& arguments) {	
	smatch matchResults;

	CommandType commandType = categorizeAddCommand(arguments, matchResults);

	switch (commandType) {
	case ADD_FLOATING_TASK:
		makeParsedCommandAddFloatingTask(parsedCommand, matchResults);
		break;
	case ADD_DEADLINE_TASK:
		makeParsedCommandAddDeadlineTask(parsedCommand, matchResults);
		break;
	case ADD_TIMED_TASK:
		makeParsedCommandAddTimedTask(parsedCommand, matchResults);
		break;
	case INVALID:
		makeParsedCommandAddErrorSyntax(parsedCommand, matchResults);
		break;
	default:
		throw (range_error("Unexpected commandType for 'add' instruction"));
	}
}

ParserAdd::CommandType ParserAdd::categorizeAddCommand(const string& arguments, 
												 smatch& matchingResults) {
	CommandType returnedCommandType;

	if (regex_match(arguments, matchingResults, regex(regexArgumentAddTimed))) {
		returnedCommandType = ADD_TIMED_TASK;
	}else if (regex_match(arguments, matchingResults, regex(regexArgumentAddDeadline))) {
		returnedCommandType = ADD_DEADLINE_TASK;
	}else if (regex_match(arguments, matchingResults, regex(regexArgumentAddFloating))) {
		returnedCommandType = ADD_FLOATING_TASK;
	}else {
		returnedCommandType = INVALID;
	}
		
	return returnedCommandType;
}

void ParserAdd::makeParsedCommandAddFloatingTask(PARSED_COMMAND& parsedCommand, 
											  smatch& matchingArguments) {

	parsedCommand.commandType = ADD_FLOATING_TASK;
	parsedCommand.addTaskName = matchingArguments[ARGUMENT_POS_ADD_TASKNAME];

}

void ParserAdd::makeParsedCommandAddDeadlineTask(PARSED_COMMAND& parsedCommand, 
											  smatch& matchingArguments) {

	parsedCommand.commandType = ADD_DEADLINE_TASK;
	parsedCommand.addTaskName = matchingArguments[ARGUMENT_POS_ADD_TASKNAME];
	ptime endTime = parseDateTime(matchingArguments[ARGUMENT_POS_ADD_FIRSTTIME]);
	parsedCommand.addEndTime = endTime;
	
	// Check for errors when parsing the time
	if (endTime.is_not_a_date_time()) {
		
		parsedCommand.commandType = INVALID;
		parsedCommand.errorType = PARSED_COMMAND::ERROR_FORMAT_DATE;
	}
}

void ParserAdd::makeParsedCommandAddTimedTask(PARSED_COMMAND& parsedCommand, 
										   smatch& matchingArguments) {
	parsedCommand.commandType = ADD_TIMED_TASK;
	parsedCommand.addTaskName = matchingArguments[ARGUMENT_POS_ADD_TASKNAME];
	ptime startTime = parseDateTime(matchingArguments[ARGUMENT_POS_ADD_FIRSTTIME]);
	ptime endTime = parseDateTime(matchingArguments[ARGUMENT_POS_ADD_SECONDTIME]);

	parsedCommand.addStartTime = startTime;
	parsedCommand.addEndTime = endTime;

	// Check for errors when parsing the time
	if (endTime.is_not_a_date_time() || startTime.is_not_a_date_time()) {
		parsedCommand.commandType = INVALID;
		parsedCommand.errorType = PARSED_COMMAND::ERROR_FORMAT_DATE;
	}
}

void ParserAdd::makeParsedCommandAddErrorSyntax(PARSED_COMMAND& parsedCommand, 
											  smatch& matchingArguments) {
	parsedCommand.commandType = INVALID;
	parsedCommand.errorType = parsedCommand.ERROR_FORMAT_ADD;

}
